package unidades;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import mapa.Coordenada;
import mapa.Mapa;

/**
 * Clase abstracta que es la superclase de cualquier unidad con la
 * que se pueda jugar
 *
 * @author Hasier Rodriguez
 */
public abstract class UnidadHT extends Unidad {

    private int id;
    public final int VIDA_MAX;
    public final int ATAQUE;
    public final int DEFENSA;
    public final int DIST_ATAQ_MIN;
    public final int DIST_ATAQ_MAX;
    public final int MOVILIDAD;
    private int vidaPartida;
    //Son protected para dejar a las unidades que hereden hacer habilidades especiales
    //como atacar, mover y atacar, regenerar y atacar...
    protected boolean puedeAtacar;
    protected boolean puedeMover;
    protected boolean puedeRegenerar;

    /**
     * Constructor que recibe todos los parámetros necesarios para crear una unidad.
     * Inicializa por defecto la vida de la partida al total
     *
     * @param vidaMax Total de vida
     * @param atq Ataque
     * @param def Defensa
     * @param dAMin Distancia mínima de ataque
     * @param dAMax Distancia máxima de ataque
     * @param mov Movimiento
     */
    public UnidadHT(int id, int vidaMax, int atq, int def, int dAMin, int dAMax, int mov) {
        this(id, vidaMax, atq, def, dAMin, dAMax, mov, vidaMax);
    }

    /**
     * Constructor que recibe todos los parámetros necesarios para crear una unidad.
     * Inicializa por defecto la vida de la partida al total
     *
     * @param vidaMax Total de vida
     * @param atq Ataque
     * @param def Defensa
     * @param dAMin Distancia mínima de ataque
     * @param dAMax Distancia máxima de ataque
     * @param mov Movimiento
     * @param vida Vida actual de la unidad
     * @param x Posición en la coordenada x
     * @param y Posición en la coordenada y
     */
    public UnidadHT(int id, int vidaMax, int atq, int def, int dAMin, int dAMax, int mov, int x, int y) {
        this(id, vidaMax, atq, def, dAMin, dAMax, mov, vidaMax, x, y);
    }

    /**
     * Constructor que recibe todos los parámetros necesarios para crear una unidad.
     * Inicializa por defecto la vida de la partida al total
     *
     * @param vidaMax Total de vida
     * @param atq Ataque
     * @param def Defensa
     * @param dAMin Distancia mínima de ataque
     * @param dAMax Distancia máxima de ataque
     * @param mov Movimiento
     */
    public UnidadHT(int id, int vidaMax, int atq, int def, int dAMin, int dAMax, int mov, int vida) {
        //this(id, vidaMax, atq, def, dAMin, dAMax, mov, vida, 0, 0);
        this.id = id;
        VIDA_MAX = vidaMax;
        ATAQUE = atq;
        DEFENSA = def;
        DIST_ATAQ_MIN = dAMin;
        DIST_ATAQ_MAX = dAMax;
        MOVILIDAD = mov;
        setVidaPartida(vida);
        puedeAtacar = true;
        puedeMover = true;
        puedeRegenerar = true;
    }

    /**
     * Constructor que recibe todos los parámetros necesarios para crear una unidad
     *
     * @param vidaMax Total de vida
     * @param atq Ataque
     * @param def Defensa
     * @param dAMin Distancia mínima de ataque
     * @param dAMax Distancia máxima de ataque
     * @param mov Movimiento
     * @param vida Vida actual de la unidad
     * @param x Posición en la coordenada x
     * @param y Posición en la coordenada y
     * @throws IllegalArgumentException si el argumento de la vida actual es mayor
     * que la vida total
     */
    public UnidadHT(int id, int vidaMax, int atq, int def, int dAMin, int dAMax, int mov, int vida, int x, int y) {
        this(id, vidaMax, atq, def, dAMin, dAMax, mov, vida);
        setPosicion(new Coordenada(x, y));
    }

    /**
     * Devuelve la vida de la unidad en la partida actual
     *
     * @return La vida de la partida
     */
    public int getVidaPartida() {
        return vidaPartida;
    }

    /**
     * Establece la vida actual de la partida de esta unidad
     *
     * @param vidaPartida La nueva vida de la unidad
     */
    public void setVidaPartida(int vidaPartida) {
        if (vidaPartida > VIDA_MAX || vidaPartida < 0) {
            throw new IllegalArgumentException("La vida actual (" + vidaPartida + ") no puede ser mayor que la total (" + VIDA_MAX + ") ni menor que 0");
        }
        this.vidaPartida = vidaPartida;
    }

    /**
     * Devuelve una cadena de texto con la localización de la interfaz
     * que otorga la afinidad a esta unidad (paquete.subpaquete...interfaz)
     * para poder instanciarla de forma dinámica (reflectividad)
     *
     * @return un String que contiene la localización de la interfaz
     * que otorga la afinidad
     */
    public abstract String getAfinidad();

    /**
     * Devuelve el coste en puntos de esta unidad
     * 
     * @return Los puntos que cuesta esta unidad
     */
    public abstract int getCoste();

    /**
     * Devuelve si la unidad puede moverse
     *
     * @return
     */
    public boolean puedeMover() {
        return puedeMover;
    }

    /**
     * Devuelve si la unidad puede atacar
     *
     * @return
     */
    public boolean puedeAtacar() {
        return puedeAtacar;
    }

    /**
     * Devuelve si la unidad se puede regenerar
     *
     * @return
     */
    public boolean puedeRegenerar() {
        if (vidaPartida >= VIDA_MAX) {
            return false;
        } else {
            return puedeRegenerar;
        }
    }

    /**
     * Devuelve si la unidad puede realizar alguna acción
     *
     * @return
     */
    public boolean puedeActuar() {
        return (puedeAtacar || puedeMover || puedeRegenerar);
    }

    /**
     * Establece esta unidad como que ha movido
     */
    public void movido() {
        puedeMover = false;
        puedeRegenerar = false;
    }

    /**
     * Establece esta unidad como que ha atacado. No puede realizar más acciones
     */
    public void atacado() {
        bloquear();
    }

    /**
     * Establece esta unidad como regenerada. No puede realizar más acciones
     */
    public void regenerado() {
        bloquear();
    }

    /**
     * Bloquea esta unidad, no permitiendo más acciones
     */
    public void bloquear() {
        puedeAtacar = false;
        puedeMover = false;
        puedeRegenerar = false;
    }

    /**
     * Reinicia los estados de movimiento y ataque permitiéndo volver a atacar o mover
     */
    public void resetState() {
        puedeAtacar = true;
        puedeMover = true;
        puedeRegenerar = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.getClass().getSimpleName();
    }

    /**
     * Devuelve el area que rodea a la unidad en la distancia indicada.
     * También elimina todas las coordenadas que estén repetidas
     * 
     * @param distancia La distancia máxima entre un extremo del área devuelta y la posición de la unidad
     * @param mapa Mapa sobre el que está la unidad
     * @return El área que rodea a la unidad en la distancia indicada
     */
    private Collection<Coordenada> getArea(int distancia, Mapa mapa) {
        int mov = distancia;
        Coordenada maxCoord = mapa.getCoordenadaMaxima();
        ArrayList<Coordenada> area = new ArrayList<Coordenada>();
        for (; mov > 0; mov--) {
            for (int i = super.getPosicion().getX() - mov; i <= getPosicion().getX() + mov; i++) {
                int y = getPosicion().getY() - (distancia - mov);
                if (y <= maxCoord.getY() && y >= 0 && i <= maxCoord.getY() && i >= 0) {
                    area.add(new Coordenada(i, y));
                }
                y = getPosicion().getY() + (distancia - mov);
                if (y <= maxCoord.getY() && y >= 0 && i <= maxCoord.getY() && i >= 0) {
                    area.add(new Coordenada(i, y));
                }
            }
            for (int i = getPosicion().getY() - mov; i <= getPosicion().getY() + mov; i++) {
                int x = getPosicion().getX() - (distancia - mov);
                if (x <= maxCoord.getX() && x >= 0 && i <= maxCoord.getX() && i >= 0) {
                    area.add(new Coordenada(x, i));
                }
                x = getPosicion().getX() + (distancia - mov);
                if (x <= maxCoord.getX() && x >= 0 && i <= maxCoord.getX() && i >= 0) {
                    area.add(new Coordenada(x, i));
                }
            }
        }
        //Pasamos todas las coordenadas a un Set, ya que no admite duplicados
        HashSet h = new HashSet(area);
        //Borramos todo lo que hay en el area
        area.clear();
        //y anyadimos todo lo que hay en el Set, es decir, las coordenadas sin duplicados
        area.addAll(h);
        area.remove(this.getPosicion());
        return area;
    }

    /**
     * Devuelve las coordenadas a las que se puede mover la unidad
     * 
     * @param mapa Mapa en el que está la unidad
     * @return Las coordenadas a las que la unidad se puede mover
     */
    public Collection<Coordenada> getDistMovimiento(Mapa mapa) {
        Collection<Coordenada> areaMov = this.getArea(MOVILIDAD, mapa);
        //Nuevo ArrayList de coordenadas que contendrá aquellas posiciones
        //en el mapa que están ocupadas y a las que no se puede mover
        Collection<Coordenada> coordOcupadas = new ArrayList<Coordenada>();
        Iterator<Coordenada> iterator = areaMov.iterator();
        while (iterator.hasNext()) {
            Coordenada c = iterator.next();
            if (mapa.getContenidoCasilla(c) != null) {
                coordOcupadas.add(c);
            }
        }
        areaMov.removeAll(coordOcupadas);
        return areaMov;
    }

    /**
     * Devuelve las coordenadas a las que puede atacar la unidad
     * 
     * @param mapa Mapa en el que está la unidad
     * @return Las coordenadas a las que la unidad puede atacar
     */
    public Collection<Coordenada> getAreaAtaque(Mapa mapa) {
        Collection<Coordenada> area = this.getArea(DIST_ATAQ_MAX, mapa);
        area.removeAll(this.getArea(DIST_ATAQ_MIN, mapa));
        return area;
    }

    /**
     * Devuelve la id de la unidad.
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * Establece si esta unidad se puede mover
     * 
     * @param pm 
     */
    public void setPuedeMover(boolean pm) {
        puedeMover = pm;
    }

    /**
     * Establece si esta unidad puede atacar
     * 
     * @param pa 
     */
    public void setPuedeAtacar(boolean pa) {
        puedeAtacar = pa;
    }

    /**
     * Establece si esta unidad se puede regenerar
     * 
     * @param pr 
     */
    public void setPuedeRegenerar(boolean pr) {
        puedeRegenerar = pr;
    }

    /**
     * Devuelve la URL que apunta a la imagen de esta unidad
     *
     * @param ext La extension de la imagen (.png, .gif...)
     * @return La URL de la imagen
     */
    public URL getImage(String ext) {
        return this.getClass().getResource("/resources/" + getId() + ext);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        if (!super.equals(obj)) {
            return false;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof UnidadHT)) {
            return false;
        }
        UnidadHT other = (UnidadHT) obj;
        if (this.id != other.id) {
            return false;
        }
        if (this.vidaPartida != other.vidaPartida) {
            return false;
        }
        if (this.puedeAtacar != other.puedeAtacar) {
            return false;
        }
        if (this.puedeMover != other.puedeMover) {
            return false;
        }
        if (this.puedeRegenerar != other.puedeRegenerar) {
            return false;
        }
        return true;
    }
}
